package r3.rpc.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import r3.rpc.InvokePolicy;
import r3.rpc.RpcRequest;
import r3.rpc.protocol.codec.RpcDecoder;
import r3.rpc.protocol.codec.RpcEncoder;
import r3.rpc.protocol.serialization.HessianSerialization;

import java.util.concurrent.CountDownLatch;

/**
 * NettyServer
 *
 * @author zhoufn
 * @create 2017-12-25 10:37
 **/
@Slf4j
public class NettyServer implements Server {

    /**
     * boss工作线程数
     */
    private int bossThreadCount = 1;

    /**
     * worker工作线程数
     */
    private int workerThreadCount = 1;

    /**
     * 监听地址
     */
    private String listenHost;

    /**
     * 监听端口
     */
    private int listenPort;

    /**
     * 反射策略
     */
    private Class<? extends InvokePolicy> invokePolicyClass;


    public NettyServer(Class<? extends InvokePolicy> invokePolicyClass) {
        this.invokePolicyClass = invokePolicyClass;
    }

    public NettyServer(String listenHost, int listenPort,Class<? extends InvokePolicy> invokePolicyClass) {
        this(invokePolicyClass);
        this.listenHost = listenHost;
        this.listenPort = listenPort;
    }

    public NettyServer(int bossThreadCount, int workerThreadCount, String listenHost, int listenPort,Class<? extends InvokePolicy> invokePolicyClass) {
        this(listenHost,listenPort,invokePolicyClass);
        this.listenHost = listenHost;
        this.listenPort = listenPort;
    }

    /**
     * start
     */
    public synchronized void start(CountDownLatch downLatch) {
        final EventLoopGroup bossGroup = new NioEventLoopGroup(this.bossThreadCount);
        final EventLoopGroup workerGroup = new NioEventLoopGroup(this.workerThreadCount);
        /**
         * 在jvm退出时，确保netty服务器安全退出，注意退出是指ctrl+c或者kill -15，如果用kill -9 那是没办法的
         */
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            if (!workerGroup.isShutdown()) {
                workerGroup.shutdownGracefully();
            }
            if (!bossGroup.isShutdown()) {
                bossGroup.shutdownGracefully();
            }
            log.debug("HOOK：RPC服务器已关闭");
        }) {
        });
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(new RpcEncoder(new HessianSerialization()))
                                    .addLast(new RpcDecoder(new HessianSerialization(),RpcRequest.class))
                                    .addLast(new NettyRpcHandler(getInvokePolicyClass()));
                        }
                    });
            ChannelFuture future = bootstrap.bind(this.listenHost,this.listenPort).sync();
            log.debug("服务器已启动（端口号：{}）", this.listenPort);
            downLatch.countDown();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("启动服务器过程中发生异常", e);
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    /**
     * stop
     */
    public synchronized void stop() {
        log.info("netty服务器关闭");
    }

    /**
     * 获取反射策略
     *
     * @return
     */
    public Class<? extends InvokePolicy> getInvokePolicyClass() {
        return this.invokePolicyClass;
    }
}
